package crmdna.federatedsecurity;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Date;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.googlecode.objectify.ObjectifyFilter;
import com.microtripit.mandrillapp.lutung.model.MandrillApiError;

import crmdna.client.Client;
import crmdna.client.ClientProp;
import crmdna.common.Utils;
import crmdna.common.api.APIException;
import crmdna.common.api.APIResponse.Status;
import crmdna.group.Group;
import crmdna.group.Group.GroupProp;
import crmdna.mail2.Mail;
import crmdna.mail2.MailContent;
import crmdna.mail2.MailContent.ReservedMailContentName;
import crmdna.mail2.MailTest;
import crmdna.mail2.SentMailEntity;
import crmdna.mail2.SentMailQueryCondition;
import crmdna.member.Account.EmailVerificationResult;
import crmdna.member.Account.LoginResult;
import crmdna.user.User;
import crmdna.user.User.Action;
import crmdna.user.User.ResourceType;
import crmdna.user.UserCore.UserProp;

public class SecurityTest {
  private final LocalServiceTestHelper datastoreHelper = new LocalServiceTestHelper(
      new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy());
  // local implementation / test harness implementation becomes HRD
  // only if setApplyAllHighRepJobPolicy is set. If the implementation is not
  // HRD then
  // cross group transactions would fail (as master slave does not support it)

  String client = "isha";
  GroupProp sgp;
  UserProp validUser;
  UserProp userWithVerifyEmailPermission;

  @Before
  public void setUp() {
    datastoreHelper.setUp();
    ObjectifyFilter.complete();

    Client.create(client);

    GroupProp sgp = Group.create(client, "Singapore", User.SUPER_USER);
    assertEquals(1, sgp.groupId);

    validUser = User.create(client, "validuser@dummy.com", sgp.groupId, User.SUPER_USER);
    userWithVerifyEmailPermission =
        User.create(client, "userwithverifyemailpermission@dummy.com", sgp.groupId, User.SUPER_USER);
    User.addOrDeletePrivilege2(client, userWithVerifyEmailPermission.email, ResourceType.CLIENT,
        client, Action.VERIFY_EMAIL, true, User.SUPER_USER);

    MailContent.create(client, ReservedMailContentName.EMAIL_VERIFICATION.toString(), 0,
        "Bhairavi Yoga: Email Verification",
        "Hello, Your email verification code is: *|VERIFICATIONCODE|*", User.SUPER_USER);

    Client.addOrDeleteAllowedEmailSender(client, "verify@verify.com", "Isha Yoga", true,
        User.SUPER_USER);
    ClientProp clientProp =
        Client
            .setVerificationEmailSender(client, "verify@verify.com", "Isha Yoga", User.SUPER_USER);
    assertEquals("verify@verify.com", clientProp.verificationEmailSender);

  }

  @After
  public void tearDown() {
    ObjectifyFilter.complete();
    datastoreHelper.tearDown();
  }

  @Test(expected = APIException.class)
  public void cannotCreateAccountForInvalidClient() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    Security.createAccount("invalidclient", "sathya.t@ishafoundation.org", "pass123");
  }

  @Test(expected = APIException.class)
  public void cannotCreateAccountWithInvalidEmail() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    Security.createAccount(client, "invalidemail", "pass123");
  }

  @Test(expected = APIException.class)
  public void cannotCreateAccountWithPasswordLessThan4Char() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    Security.createAccount(client, "sathya.t@ishafoundation.org", "123");
  }

  @Test(expected = APIException.class)
  public void cannotCreateAccountWithPasswordMoreThan50Char() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    Security.createAccount(client, "sathya.t@ishafoundation.org",
        "123456789123456789123456789123456789123456789123456789123456789123456789123456789");
  }

  @Test
  public void cannotCreateAccountWithDuplicateEmail() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    Security.createAccount(client, email, "pass123");

    try {
      Security.createAccount(client, email, "pass123");
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_RESOURCE_ALREADY_EXISTS, ex.statusCode);
    }
  }

  @Test
  public void canCreateAccountWithValidEmailAndPassword() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    Security.createAccount(client, email, "pass123");

    Security.safeGetCredential(email);
    // no exception
  }

  @Test
  public void verficationEmailSentWhenNewAccountCreated() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    MailTest.suppressEmailInTestEnv();
    String email = "sathya.t@ishafoundation.org";
    Security.createAccount(client, email, "pass123");

    SentMailQueryCondition qc = new SentMailQueryCondition();
    qc.email = email;
    List<SentMailEntity> sentMailEntities =
        Mail.queryEntitiesSortedByTimeDesc(client, qc, User.SUPER_USER);
    assertEquals(1, sentMailEntities.size());

    long mailContentId = sentMailEntities.get(0).toProp().mailContentId;
    String mailContentName = MailContent.safeGet(client, mailContentId).toProp().name;

    String expectedMailContentName =
        Utils.removeSpaceUnderscoreBracketAndHyphen(ReservedMailContentName.EMAIL_VERIFICATION
            .toString().toLowerCase());
    assertEquals(expectedMailContentName, mailContentName);
  }

  @Test
  public void correctErrorWhenVerificationEmailSetupMissing() {
    assertTrue(false);
  }

  @Test
  public void verificationEmailShouldContainMergeTagVERIFICATIONCODE() {
    assertTrue(false);
  }

  @Test
  public void verificationEmailShouldNotContainMergeTagFNAME() {
    assertTrue(false);
  }

  @Test
  public void verificationEmailShouldNotContainMergeTagLNAME() {
    assertTrue(false);
  }

  @Test
  public void clientShouldHaveValidVerificationEmailSender() {
    assertTrue(false);
  }

  @Test
  public void groupZeroHaveVerificationEmailSenderAsAllowedSender() {
    assertTrue(false);
  }

  @Test
  public void cannotLoginWithNonexistingEmail() {
    String email = "sathya.t@ishafoundation.org";

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.EMAIL_NOT_A_VALID_ACCOUNT, loginResult);
  }

  @Test
  public void cannotLoginIfEmailIsUnverified() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(email, email, "pass123");

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.EMAIL_NOT_VERIFIED, loginResult);
  }

  @Test
  public void canVerifyEmailWithCorrectVerificationCode() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(email, email, "pass123");

    long verificationCode = Security.safeGetCredential(email).verificationCode;

    EmailVerificationResult result = Security.verifyEmail(client, email, verificationCode);
    assertEquals(EmailVerificationResult.SUCCESS, result);
  }

  @Test
  public void cannotVerifyEmailWithIncorrectVerificationCode() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(email, email, "pass123");

    long verificationCode = Security.safeGetCredential(email).verificationCode;

    EmailVerificationResult result = Security.verifyEmail(client, email, verificationCode + 100);
    assertEquals(EmailVerificationResult.WRONG_VERIFICATION_CODE, result);
  }

  @Test
  public void cannotVerifyEmailWithNoAccount() {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();

    EmailVerificationResult result = Security.verifyEmail(client, email, 100);
    assertEquals(EmailVerificationResult.EMAIL_NOT_A_VALID_ACCOUNT, result);
  }

  @Test
  public void cannotLoginWithIncorrectCredential() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, User.SUPER_USER);

    LoginResult loginResult = Security.login(client, email, "invalid password");
    assertEquals(LoginResult.WRONG_CREDENTIAL, loginResult);
  }

  @Test
  public void userWOPermissionCannotSetAccountToVerified() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    try {
      Security.setEmailAsVerified(client, email, validUser.email);
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_INSUFFICIENT_PERMISSION, ex.statusCode);
    }
  }

  @Test
  public void userWithPermissionCanSetAccountToVerified() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    CredentialProp credentialProp = Security.safeGetCredential(email).toProp();
    assertEquals(false, credentialProp.isEmailVerified);

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    credentialProp = Security.safeGetCredential(email).toProp();
    assertEquals(true, credentialProp.isEmailVerified);
  }

  @Test
  public void accountGetsBlockedWithMultipleUnsuccessfulLogins() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    for (int i = 0; i < 24; i++) {
      LoginResult loginResult = Security.login(client, email, "invalidpassword");
      assertEquals(LoginResult.WRONG_CREDENTIAL, loginResult);
    }

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.ACCOUNT_BLOCKED, loginResult);
  }

  @Test
  public void accountBlockedDueToRepeatedUnsuccessfulLoginIsReleasedAfter24Hours()
      throws NoSuchAlgorithmException, InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    for (int i = 0; i < 24; i++) {
      LoginResult loginResult = Security.login(client, email, "invalidpassword");
      assertEquals(LoginResult.WRONG_CREDENTIAL, loginResult);
    }

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.ACCOUNT_BLOCKED, loginResult);

    // manually set time to now - 18 hours
    final int MILLI_SECONDS_IN_HOUR = 3600 * 1000;
    CredentialEntity credentialEntity = Security.safeGetCredential(email);
    credentialEntity.lastUnsuccessfulLoginMS = new Date().getTime() - 18 * MILLI_SECONDS_IN_HOUR;

    loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.ACCOUNT_BLOCKED, loginResult);

    credentialEntity.lastUnsuccessfulLoginMS = new Date().getTime() - 24 * MILLI_SECONDS_IN_HOUR;
    loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.SUCCESS, loginResult);
  }

  @Test
  public void alertEmailSentWhenAccountBlocked() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    for (int i = 0; i < 24; i++) {
      LoginResult loginResult = Security.login(client, email, "invalidpassword");
      assertEquals(LoginResult.WRONG_CREDENTIAL, loginResult);
    }

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.ACCOUNT_BLOCKED, loginResult);

    SentMailQueryCondition qc = new SentMailQueryCondition();
    qc.email = email;
    List<SentMailEntity> sentMailEntities =
        Mail.queryEntitiesSortedByTimeDesc(email, qc, User.SUPER_USER);
    assertEquals(1, sentMailEntities.size());

    long mailContentId = sentMailEntities.get(0).toProp().mailContentId;
    String mailContentName = MailContent.safeGet(client, mailContentId).toProp().name;
    assertEquals("accountblocked", mailContentName);
  }

  @Test
  public void canLoginWithCorrectCredential() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.SUCCESS, loginResult);
  }

  @Test
  public void emailSentForPasswordResetWithNewPassword() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    String newPassword = Security.resetPassword(client, email);

    SentMailQueryCondition qc = new SentMailQueryCondition();
    qc.email = email;
    List<SentMailEntity> sentMailEntities =
        Mail.queryEntitiesSortedByTimeDesc(email, qc, User.SUPER_USER);
    assertEquals(1, sentMailEntities.size());

    long mailContentId = sentMailEntities.get(0).toProp().mailContentId;
    String mailContentName = MailContent.safeGet(client, mailContentId).toProp().name;
    assertEquals("passwordreset", mailContentName);

    String actualBody = sentMailEntities.get(0).toProp().getActualBody();
    assertTrue(actualBody.contains(newPassword));
  }

  @Test
  public void passwordCannotBeResetAgainWithin5Min() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    Security.resetPassword(client, email);

    try {
      Security.resetPassword(client, email);
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_OPERATION_NOT_ALLOWED, ex.statusCode);
    }
  }

  @Test
  public void passwordCanBeResetAgainAfter5Min() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    Security.resetPassword(client, email);

    CredentialEntity credentialEntity = Security.safeGetCredential(email);
    final int MILLISECONDS_IN_A_MINUTE = 60 * 1000;
    credentialEntity.lastPasswordResetMS = new Date().getTime() - 6 * MILLISECONDS_IN_A_MINUTE;

    String newPassword = Security.resetPassword(client, email);

    LoginResult loginResult = Security.login(client, email, newPassword);
    assertEquals(LoginResult.SUCCESS, loginResult);
  }

  @Test
  public void canLoginWithNewPasswordAfterReset() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    String newPassword = Security.resetPassword(client, email);

    LoginResult loginResult = Security.login(client, email, newPassword);
    assertEquals(LoginResult.SUCCESS, loginResult);
  }

  @Test
  public void cannotChangeToInvalidPassword() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    // too short
    try {
      Security.changePassword(client, email, "pass123", "123");
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_RESOURCE_INCORRECT, ex.statusCode);
    }

    // too long
    try {
      Security
          .changePassword(
              client,
              email,
              "pass123",
              "123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789");
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_RESOURCE_INCORRECT, ex.statusCode);
    }
  }

  @Test
  public void cannotChangePasswordWithoutExistingPassword() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    try {
      Security.changePassword(client, email, "wrongpassword", "newpassword");
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_AUTH_FAILURE, ex.statusCode);
    }
  }

  @Test
  public void cannotChangeToSamePassword() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    try {
      Security.changePassword(client, email, "pass123", "pass123");
      assertTrue(false);
    } catch (APIException ex) {
      assertEquals(Status.ERROR_RESOURCE_INCORRECT, ex.statusCode);
    }
  }

  @Test
  public void canChangeToValidPassword() throws NoSuchAlgorithmException, InvalidKeySpecException,
      MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    Security.changePassword(client, email, "pass123", "pass456");

    LoginResult loginResult = Security.login(client, email, "pass456");
    assertEquals(LoginResult.SUCCESS, loginResult);
  }

  @Test
  public void cannotLoginWithOldPassword() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    Security.changePassword(client, email, "pass123", "pass456");

    LoginResult loginResult = Security.login(client, email, "pass123");
    assertEquals(LoginResult.WRONG_CREDENTIAL, loginResult);
  }

  @Test
  public void emailNotificationSentForPasswordChange() throws NoSuchAlgorithmException,
      InvalidKeySpecException, MandrillApiError, IOException {
    String email = "sathya.t@ishafoundation.org";
    MailTest.suppressEmailInTestEnv();
    Security.createAccount(client, email, "pass123");

    Security.setEmailAsVerified(client, email, userWithVerifyEmailPermission.email);

    Security.changePassword(client, email, "pass123", "pass456");

    SentMailQueryCondition qc = new SentMailQueryCondition();
    qc.email = email;
    List<SentMailEntity> sentMailEntities =
        Mail.queryEntitiesSortedByTimeDesc(email, qc, User.SUPER_USER);
    assertEquals(1, sentMailEntities.size());

    long mailContentId = sentMailEntities.get(0).toProp().mailContentId;
    String mailContentName = MailContent.safeGet(client, mailContentId).toProp().name;
    assertEquals("passwordchange", mailContentName);
  }

  @Test
  public void emailNotificationForPasswordChangeHasResetPasswordLink() {
    assertTrue(false);
  }
}
